package com.george.es.client;

import com.alibaba.fastjson.JSON;
import com.george.es.model.dto.UserDto;
import com.george.es.model.entity.Article;
import io.searchbox.cluster.Health;
import io.searchbox.cluster.NodesInfo;
import io.searchbox.cluster.NodesStats;
import io.searchbox.core.*;
import io.searchbox.core.SearchResult.Hit;

import com.google.common.collect.Lists;
import com.google.gson.GsonBuilder;
import com.xiaoleilu.hutool.date.DateUtil;
import io.searchbox.client.JestClient;
import io.searchbox.client.JestClientFactory;
import io.searchbox.client.JestResult;
import io.searchbox.client.config.HttpClientConfig;
import io.searchbox.indices.*;
import io.searchbox.indices.mapping.PutMapping;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.junit.After;
import org.junit.Before;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * <p>
 *  JestClient客户端
 * </p>
 *
 * @author GeorgeChan 2019/6/13 17:29
 * @version 1.0
 * @since jdk1.8
 */
public class JestClientUtil {
    public static JestClient jestClient;
    private static String elasticIps="http://192.168.116.136:9200";
    private static String indexName = "article";
    private static String typeName = "content";

    @Before
    public static JestClient getClient() throws Exception{
        JestClientFactory factory = new JestClientFactory();
        factory.setHttpClientConfig(new HttpClientConfig
                .Builder(elasticIps)
                .gson(new GsonBuilder().setDateFormat(DateUtil.NORM_DATETIME_PATTERN).create())
                .connTimeout(1500)
                .readTimeout(3000)
                .multiThreaded(true)
                .build());
        jestClient=factory.getObject();
        return jestClient;
    }

    @After
    public static void tearDown() throws Exception {
        closeJestClient(jestClient);
    }

    /**
     * 关闭JestClient客户端
     * @param jestClient
     * @throws Exception
     */
    public static void closeJestClient(JestClient jestClient) throws Exception {

        if (jestClient != null) {
            jestClient.close();
        }
    }

    /**
     * 创建索引
     * @param jestClient 客户端
     * @param indexName 索引名称
     * @return 是否成功
     * @throws Exception 异常
     */
    public boolean createIndex(JestClient jestClient, String indexName) throws Exception {
        JestResult jr = jestClient.execute(new CreateIndex.Builder(indexName).build());
        return jr.isSucceeded();
    }

    /**
     * Put映射
     * @param jestClient 客户端
     * @param indexName 索引名称
     * @param typeName 类型名称
     * @param source 映射源
     * @return 是否成功
     * @throws Exception 异常
     */
    public boolean createIndexMapping(JestClient jestClient, String indexName, String typeName, String source) throws Exception {
//        String source = "{\"" + typeName + "\":{\"properties\":{"
//                + "\"author\":{\"type\":\"text\",\"index\":\"false\"}"
//                + ",\"title\":{\"type\":\"text\"}"
//                + ",\"content\":{\"type\":\"text\"}"
//                + ",\"price\":{\"type\":\"text\"}"
//                + ",\"view\":{\"type\":\"text\"}"
//                + ",\"tag\":{\"type\":\"text\"}"
//                + ",\"date\":{\"type\":\"date\",\"format\":\"yyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis\"}"
//                + "}}}";
        System.out.println(source);
        PutMapping putMapping = new PutMapping.Builder(indexName, typeName, source).build();
        JestResult jr = jestClient.execute(putMapping);
        return jr.isSucceeded();
    }

    /**
     * Bulk插入数据
     * @param jestClient 客户端
     * @param indexName 索引名称
     * @param typeName 类型名称
     * @param objects 数据源
     * @return 是否成功
     * @throws Exception 异常
     */
    public String bulkIndex(JestClient jestClient, String indexName, String typeName, List<Object> objects) throws IOException {
        Bulk.Builder bulk = new Bulk.Builder().defaultIndex(indexName).defaultType(typeName);
        for (Object obj : objects) {
            Index index = new Index.Builder(obj).build();
            bulk.addAction(index);
        }
        JestResult jestResult=jestClient.execute(bulk.build());
        System.out.println(jestResult.getJsonString());
        return jestResult.getJsonString();
    }

    /**
     * 单值完全匹配查询
     * @param jestClient 客户端
     * @param indexName 索引名称
     * @param typeName 类型名称
     * @param queryName 查询的字段
     * @param queryValue 搜索查询的值
     * @param from 页码
     * @param size 页大小
     * @return 查询的数据
     * @throws Exception 异常
     */
    public List<Object> termQuery(JestClient jestClient, String indexName, String typeName, String queryName, String queryValue, int from, int size) throws Exception {

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        QueryBuilder queryBuilder = QueryBuilders
                .termQuery(queryName, queryValue);//单值完全匹配查询
        searchSourceBuilder.query(queryBuilder);
        searchSourceBuilder.size(size);
        searchSourceBuilder.from(from);
        String query = searchSourceBuilder.toString();
        System.out.println(query);


        Search search = new Search.Builder(query)
                .addIndex(indexName)
                .addType(typeName)
                .build();
        SearchResult result = jestClient.execute(search);

        List<Hit<Object, Void>> hits = result.getHits(Object.class);
        System.out.println("Size:" + hits.size());
        List<Object> objects = Lists.newArrayList();
        for (Hit<Object, Void> hit : hits) {
            Object news = hit.source;
            objects.add(news);
            System.out.println(news.toString());
        }
        return objects;
    }

    /**
     * 单值完全匹配查询
     * @param jestClient 客户端
     * @param indexName 索引名称
     * @param typeName 类型名称
     * @param queryName 查询的字段
     * @param queryList 搜索查询的值
     * @param from 页码
     * @param size 页大小
     * @return 查询的数据
     * @throws Exception 异常
     */
    public static List<Object> termsQuery(JestClient jestClient, String indexName, String typeName, String queryName, List<String> queryList, int from, int size) throws Exception {

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        QueryBuilder queryBuilder = QueryBuilders
                .termsQuery(queryName, queryList.toArray());//多值完全匹配查询
        searchSourceBuilder.query(queryBuilder);
        searchSourceBuilder.size(size);
        searchSourceBuilder.from(from);
        String query = searchSourceBuilder.toString();
        System.out.println(query);

        Search search = new Search.Builder(query)
                .addIndex(indexName)
                .addType(typeName)
                .build();
        SearchResult result = jestClient.execute(search);

        List<Hit<Object, Void>> hits = result.getHits(Object.class);
        System.out.println("Size:" + hits.size());
        List<Object> objects = Lists.newArrayList();
        for (Hit<Object, Void> hit : hits) {
            Object news = hit.source;
            objects.add(news);
            System.out.println(news.toString());
        }
        return objects;
    }

    /**
     * 通配符和正则表达式查询
     * @param jestClient 客户端
     * @param indexName 索引名称
     * @param typeName 类型名称
     * @param queryName 查询的字段
     * @param expresion 正则表达式
     * @param from 页码
     * @param size 页大小
     * @return 查询的数据
     * @throws Exception 异常
     */
    public List<Object> wildcardQuery(JestClient jestClient, String indexName, String typeName, String queryName, String expresion, int from, int size) throws Exception {

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        QueryBuilder queryBuilder = QueryBuilders
                .wildcardQuery(queryName, expresion);//通配符和正则表达式查询
        searchSourceBuilder.query(queryBuilder);
        searchSourceBuilder.size(size);
        searchSourceBuilder.from(from);
        String query = searchSourceBuilder.toString();
        System.out.println(query);


        Search search = new Search.Builder(query)
                .addIndex(indexName)
                .addType(typeName)
                .build();
        SearchResult result = jestClient.execute(search);

        List<Hit<Object, Void>> hits = result.getHits(Object.class);
        System.out.println("Size:" + hits.size());
        List<Object> objects = Lists.newArrayList();
        for (Hit<Object, Void> hit : hits) {
            Object news = hit.source;
            objects.add(news);
            System.out.println(news.toString());
        }
        return objects;
    }

    /**
     * 前缀查询
     * @param jestClient 客户端
     * @param indexName 索引名称
     * @param typeName 类型名称
     * @param queryName 查询的字段
     * @param prefix 前缀
     * @param from 页码
     * @param size 页大小
     * @return 查询的数据
     * @throws Exception 异常
     */
    public static List<Object> prefixQuery(JestClient jestClient, String indexName, String typeName,
                                           String queryName, String prefix, int from, int size) throws Exception {

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        QueryBuilder queryBuilder = QueryBuilders
                .prefixQuery(queryName, prefix);//前缀查询
        searchSourceBuilder.query(queryBuilder);
        searchSourceBuilder.size(size);
        searchSourceBuilder.from(from);
        String query = searchSourceBuilder.toString();
        System.out.println(query);

        Search search = new Search.Builder(query)
                .addIndex(indexName)
                .addType(typeName)
                .build();
        SearchResult result = jestClient.execute(search);

        List<Hit<Object, Void>> hits = result.getHits(Object.class);
        System.out.println("Size:" + hits.size());
        List<Object> objects = Lists.newArrayList();
        for (Hit<Object, Void> hit : hits) {
            Object news = hit.source;
            objects.add(news);
            System.out.println(news.toString());
        }
        return objects;
    }


    /**
     * 区间查询
     * @param jestClient 客户端
     * @param indexName 索引名称
     * @param typeName 类型名称
     * @param queryName 查询的字段
     * @param timeFrom 开始时间
     * @param timeTo 结束时间
     * @param from 页码
     * @param size 页大小
     * @return 查询的数据
     * @throws Exception 异常
     */
    public List<Object> timeRangeQuery(JestClient jestClient, String indexName, String typeName, String queryName, String timeFrom, String timeTo, int from, int size) throws Exception {

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        QueryBuilder queryBuilder = QueryBuilders
                .rangeQuery(queryName)
                .gte(timeFrom)
                .lte(timeTo)
                .includeLower(true)
                .includeUpper(true);//区间查询
        searchSourceBuilder.query(queryBuilder);
        searchSourceBuilder.size(size);
        searchSourceBuilder.from(from);
        String query = searchSourceBuilder.toString();
        System.out.println(query);

        Search search = new Search.Builder(query)
                .addIndex(indexName)
                .addType(typeName)
                .build();
        SearchResult result = jestClient.execute(search);

        List<Hit<Object, Void>> hits = result.getHits(Object.class);
        System.out.println("Size:" + hits.size());
        List<Object> objects = Lists.newArrayList();
        for (Hit<Object, Void> hit : hits) {
            Object news = hit.source;
            objects.add(news);
            System.out.println(news.toString());
        }
        return objects;
    }

    /**
     * 文本检索，应该是将查询的词先分成词库中存在的词，然后分别去检索，存在任一存在的词即返回，查询词分词后是OR的关系。需要转义特殊字符
     * @param jestClient 客户端
     * @param indexName 索引名称
     * @param typeName 类型名称
     * @param queryText 查询的字段
     * @param from 页码
     * @param size 页大小
     * @return 查询的数据
     * @throws Exception 异常
     */
    public List<Object> queryString(JestClient jestClient, String indexName, String typeName, String queryText, int from, int size) throws Exception {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        QueryBuilder queryBuilder = QueryBuilders
            .queryStringQuery(QueryParser.escape(queryText));//文本检索，应该是将查询的词先分成词库中存在的词，然后分别去检索，存在任一存在的词即返回，查询词分词后是OR的关系。需要转义特殊字符
        searchSourceBuilder.query(queryBuilder);
        searchSourceBuilder.size(size);
        searchSourceBuilder.from(from);
        String query = searchSourceBuilder.toString();
        System.out.println(query);

        Search search = new Search.Builder(query)
                .addIndex(indexName)
                .addType(typeName)
                .build();
        SearchResult result = jestClient.execute(search);

        List<Hit<Object, Void>> hits = result.getHits(Object.class);
        System.out.println("Size:" + hits.size());
        List<Object> objects = Lists.newArrayList();
        for (Hit<Object, Void> hit : hits) {
            Object news = hit.source;
            objects.add(news);
            System.out.println(news.toString());
        }
        return objects;
    }

    /**
     * count查询
     * @param jestClient 客户端
     * @param indexName 索引名称
     * @param typeName 类型名称
     * @param queryList 搜索的字段集合
     * @param timeFrom 开始时间
     * @param timeTo 结束时间
     * @return 数量
     * @throws Exception 异常
     */
    public Double count(JestClient jestClient, String indexName, String typeName, List<String> queryList, String timeFrom, String timeTo) throws Exception {
//        String[] name = new String[]{ "T:o\"m-", "Jerry" };
//        String from = "2016-09-01T00:00:00";
//        String to = "2016-10-01T00:00:00";
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        QueryBuilder queryBuilder = QueryBuilders.boolQuery()
                .must(QueryBuilders.termsQuery("name", queryList))
                .must(QueryBuilders.rangeQuery("birth").gte(timeFrom).lte(timeTo));
        searchSourceBuilder.query(queryBuilder);
        String query = searchSourceBuilder.toString();
        System.out.println(query);

        Count count = new Count.Builder()
                .addIndex(indexName)
                .addType(typeName)
                .query(query)
                .build();
        CountResult results = jestClient.execute(count);

        Double counts = results.getCount();
        return counts;
    }

    /**
     * 根据IDGet文档
     * @param jestClient 客户端
     * @param indexName 索引名称
     * @param typeName 类型名称
     * @param id 文档id
     * @return 文档
     * @throws Exception 异常
     */
    public Object getById(JestClient jestClient, String indexName, String typeName, String id) throws Exception {
        Get get = new Get.Builder(indexName, id).type(typeName).build();
        JestResult result = jestClient.execute(get);

        if (result.isSucceeded()) {
            Object news = result.getSourceAsObject(Object.class);
            return news;
        }
        return null;
    }

    /**
     * 根据ID删除文档
     * @param jestClient 客户端
     * @param indexName 索引名称
     * @param typeName 类型名称
     * @param id 文档id
     * @return 文档
     * @throws Exception 异常
     */
    public boolean deleteIndexDocumentById(JestClient jestClient, String indexName, String typeName, String id) throws Exception {
        DocumentResult dr = jestClient.execute(new Delete.Builder(id).index(indexName).type(typeName).build());
        boolean result = dr.isSucceeded();
        return result;
    }

    /**
     * 删除索引
     * @param jestClient 客户端
     * @param indexName 索引名称
     * @return 文档
     * @throws Exception 异常
     */
    public boolean deleteIndex(JestClient jestClient, String indexName) throws Exception {
        JestResult jr = jestClient.execute(new DeleteIndex.Builder(indexName).build());
        boolean result = jr.isSucceeded();
        return result;
    }

    /**
     * 将删除所有的索引
     * @throws Exception
     */
//    public  void deleteIndexAll() throws Exception {
//        Cat cat = new Cat.IndicesBuilder().build();
//        DeleteIndex deleteIndex = new DeleteIndex.Builder("article").build();
//        JestResult result = jestClient.execute(deleteIndex);
//        System.out.println(result.getJsonString());
//    }

    /**
     * 清缓存
     * @param jestClient 客户端
     * @throws Exception
     */
    public  boolean clearCache(JestClient jestClient) throws Exception {
        ClearCache closeIndex = new ClearCache.Builder().build();
        JestResult result = jestClient.execute(closeIndex);
        System.out.println(result.getJsonString());
        return result.isSucceeded();
    }

    /**
     * 关闭索引
     * @param jestClient 客户端
     * @throws Exception
     */
    public boolean closeIndex(JestClient jestClient, String indexName) throws Exception {
        CloseIndex closeIndex = new CloseIndex.Builder(indexName).build();
        JestResult result = jestClient.execute(closeIndex);
        System.out.println(result.getJsonString());
        return result.isSucceeded();
    }

    /**
     * 优化索引
     * @param jestClient 客户端
     * @throws Exception
     */
    public  void optimize(JestClient jestClient) throws Exception {
        Optimize optimize = new Optimize.Builder().build();
        JestResult result = jestClient.execute(optimize);
        System.out.println(result.getJsonString());
    }

    /**
     * 优化索引
     * @param jestClient 客户端
     * @throws Exception
     */
    public boolean flush(JestClient jestClient) throws Exception {
        Flush flush = new Flush.Builder().build();
        JestResult result = jestClient.execute(flush);
        System.out.println(result.getJsonString());
        return result.isSucceeded();
    }

    /**
     * 判断索引目录是否存在
     * @param jestClient 客户端
     * @throws Exception
     */
    public  String indicesExists(JestClient jestClient, String indexName) throws Exception {
        IndicesExists indicesExists = new IndicesExists.Builder(indexName).build();
        JestResult result = jestClient.execute(indicesExists);
        return result.getJsonString();
    }

    /**
     * 查看节点信息
     * @param jestClient 客户端
     * @throws Exception
     */
    public String nodesInfo(JestClient jestClient) throws Exception {
        NodesInfo nodesInfo = new NodesInfo.Builder().build();
        JestResult result = jestClient.execute(nodesInfo);
        System.out.println(result.getJsonString());
        return result.getJsonString();
    }


    /**
     * 查看集群健康信息
     * @param jestClient 客户端
     * @throws Exception
     */
    public String health(JestClient jestClient) throws Exception {
        Health health = new Health.Builder().build();
        JestResult result = jestClient.execute(health);
        System.out.println(result.getJsonString());
        return result.getJsonString();
    }

    /**
     * 节点状态
     * @param jestClient 客户端
     * @throws Exception
     */
    public String nodesStats(JestClient jestClient) throws Exception {
        NodesStats nodesStats = new NodesStats.Builder().build();
        JestResult result = jestClient.execute(nodesStats);
        System.out.println(result.getJsonString());
        return result.getJsonString();
    }

    /**
     * 更新Document
     * @param jestClient 客户端
     * @param indexName 索引名称
     * @param typeName 类型名称
     * @param article 文档实体
     * @param id 文档id
     * @throws Exception 异常
     */
    public boolean updateDocument(JestClient jestClient, String indexName, String typeName, Article article, String id) throws Exception {
        String script = "{" +
                "    \"doc\" : {" +
                "        \"name\" : \""+article.getName()+"\"," +
                "        \"content\" : \""+article.getContent()+"\"," +
                "        \"path\" : \""+article.getPath()+"\"," +
                "        \"url\" : \""+article.getUrl()+"\"," +
                "        \"uploadTime\" : \""+article.getUploadTime()+"\"" +
                "    }" +
                "}";
        Update update = new Update.Builder(script).index(indexName).type(typeName).id(id).build();
        JestResult result = jestClient.execute(update);
        System.out.println(result.getJsonString());
        return result.isSucceeded();
    }

    /**
     * 删除Document
     * @param jestClient 客户端
     * @param indexName 索引名称
     * @param typeName 类型名称
     * @param id 文档id
     * @throws Exception 异常
     */
    public boolean deleteDocument(JestClient jestClient, String indexName, String typeName, String id) throws Exception {
        Delete delete = new Delete.Builder(id).index(indexName).type(typeName).build();
        JestResult result = jestClient.execute(delete);
        System.out.println(result.getJsonString());
        return result.isSucceeded();
    }

    /**
     * Suggestion
     * @param jestClient 客户端
     * @param suggestionName suggestionName
     * @throws Exception 异常
     */
    public List<Object> suggest(JestClient jestClient, String suggestionName) throws Exception{
        Suggest suggest = new Suggest.Builder("{" +
                "  \"" + suggestionName + "\" : {" +
                "    \"text\" : \"the amsterdma meetpu\"," +
                "    \"term\" : {" +
                "      \"field\" : \"body\"" +
                "    }" +
                "  }" +
                "}").build();
        SuggestResult suggestResult = jestClient.execute(suggest);
        System.out.println(suggestResult.isSucceeded());
        List<SuggestResult.Suggestion> suggestionList = suggestResult.getSuggestions(suggestionName);
        System.out.println(suggestionList.size());
        List<Object> objectList = Lists.newArrayList();
        for(SuggestResult.Suggestion suggestion : suggestionList){
            objectList.add(suggestion);
        }
        return objectList;
    }

    /**
     * 查询全部文档
     * @param jestClient 客户端
     * @param indexName 索引名称
     * @throws Exception 异常
     */
    public List<Object> searchAll(JestClient jestClient, String indexName) throws Exception {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchAllQuery());
        Search search = new Search.Builder(searchSourceBuilder.toString())
                .addIndex(indexName)
                .build();
        SearchResult result = jestClient.execute(search);
        System.out.println("本次查询共查到："+result.getTotal()+"篇文章！");
        List<Hit<Article,Void>> hits = result.getHits(Article.class);
        List<Object> list = Lists.newArrayList();
        for (Hit<Article, Void> hit : hits) {
            Article source = hit.source;
            list.add(source);
            System.out.println("标题："+source.getName());
            System.out.println("内容："+source.getContent());
            System.out.println("浏览数："+source.getPath());
            System.out.println("标签："+source.getUrl());
            System.out.println("作者："+ DateUtil.format(new Date(source.getUploadTime()), DateUtil.NORM_DATETIME_PATTERN));
        }
        return list;
    }

    /**
     * 删除Document
     * @param jestClient 客户端
     * @param indexName 索引名称
     * @param queryName 查询字段名称
     * @param queryValue 搜索的值
     * @param classType Class类
     * 搜索结果
     * @throws Exception 异常
     */
    public <T> List<T> searchHighlight(JestClient jestClient, String indexName, String queryName, String queryValue, Class<T> classType) throws Exception {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.termQuery(queryName, queryValue));
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field(queryName);//高亮title
        highlightBuilder.preTags("<em>").postTags("</em>");//高亮标签
        highlightBuilder.fragmentSize(500);//高亮内容长度
        searchSourceBuilder.highlighter(highlightBuilder);

        Search search = new Search.Builder(searchSourceBuilder.toString())
                .addIndex(indexName)
                .build();
        SearchResult result = jestClient.execute(search);
        System.out.println(result.getJsonString());
        System.out.println("本次查询共查到："+result.getTotal()+"篇文章！");
        List<Hit<T,Void>> hits = result.getHits(classType);

        List<T> list = Lists.newArrayList();
        for (Hit<T, Void> hit : hits) {
            T source = hit.source;
            //获取高亮后的内容
            Map<String, List<String>> highlight = hit.highlight;
            List<String> views = highlight.get(queryName);//高亮后的title
            if(views!=null){
                Field declaredField = source.getClass().getDeclaredField(queryName);
                declaredField.setAccessible(true);
                declaredField.set(source, views.get(0));
            }
            list.add(source);
        }
        return list;
    }
}
